﻿using System;
using System.Collections.Generic;

using MongoDB.Bson;
using MongoDB.Driver;

namespace MongoDBApp_CSharpCore
{
    class Program
    {
        static void Main(string[] args)
        {
            MongoDBDemo();

            Console.WriteLine("Exiting main");

            Console.ReadKey();  // Pause
        }

        private static async void MongoDBDemo()
        {
            MongoClient client = new MongoClient();

            // or use a connection string
            //MongoClient client = new MongoClient("mongodb://localhost:27017");

            // or, to connect to a replica set, with auto-discovery of the primary, supply a seed list of members
            // The client instance now holds a pool of connections to the server or servers specified in the connection string.
            //MongoClient client = new MongoClient("mongodb://localhost:27017,localhost:27018,localhost:27019");

            // Create a database OR read it if it already exists
            IMongoDatabase database = client.GetDatabase("mydb");

            // Create a collection when first document is added OR select it if it already exists
            IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>("test");

            BsonDocument doc;

            // Create a document
            doc = new BsonDocument
            {
                { "name", "Fred Bloggs" },
                { "age", 22 },
                { "course", "Software Engineering" }
            };

            await collection.InsertOneAsync(doc);

            // Insert many documents
            List<BsonDocument> documents = new List<BsonDocument>();
            for (int i = 0; i < 100; i++)
            {
                documents.Add(new BsonDocument
                {
                    { "i", i }
                });
            }
            await collection.InsertManyAsync(documents);

            // Count all the documents in the collection
            // Provide a blanl BSON document for the selection
            // criteria so they are all selected.
            long count = await collection.CountDocumentsAsync(new BsonDocument());
            Console.WriteLine("Collection contains {0} documents", count);

            // Read the first document in the collection
            doc = await collection.Find(new BsonDocument()).FirstOrDefaultAsync();
           Console.WriteLine("First document in collection is {0}", doc);

            // Read all the documents in the collection
            Console.WriteLine("Reading all the documents");

            // If the number of documents expected is relatively small you can
            // return them as a list
            //DisplayDataSmallList(await collection.Find(new BsonDocument()).ToListAsync());

            // When the number of documents expected is larger or the documents can be processed
            // as they are read (iteratively), use this approach.
            await collection.Find(new BsonDocument()).ForEachAsync(d => Console.WriteLine(d));

            // Find a specific documents
            doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("i", 55)).FirstOrDefaultAsync();
            Console.WriteLine("Finding document with i=55 {0}", doc);

            doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("i", 200000)).FirstOrDefaultAsync();  // Should fail to find document
            if (doc == null)
            {
                Console.WriteLine("Correctly falied to find document with i==20000");
            }

            // If you use First() instead of FirstOrDefault() you need to catch the exception.
            //try
            //{
            //    doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("i", 200000)).FirstAsync();  // Should fail to find document
            //}
            //catch (InvalidOperationException e)
            //{
            //    Console.WriteLine("Correctly falied to find document with i==20000");
            //}


            // Find a set of documents
            Console.WriteLine("Findling all documents with id > 50");
            await collection.Find(Builders<BsonDocument>.Filter.Gt("i", 50)).ForEachAsync(d => Console.WriteLine(d));

            Console.WriteLine("Findling all documents with id > 50 and <= 70");
            await collection.Find(Builders<BsonDocument>.Filter
                            .And
                            (
                                Builders<BsonDocument>.Filter.Gt("i", 50), 
                                Builders<BsonDocument>.Filter.Lte("i", 70)
                            ))
                            .ForEachAsync(d => Console.WriteLine(d));


            // Read and sort the documents
            Console.WriteLine("Sort and display the documents that have the top 5 values of i");
            var filter = Builders<BsonDocument>.Filter.Exists("i");     // Only including documents with an "i" field
            var sort = Builders<BsonDocument>.Sort.Descending("i");     // Sort by "i" field in descending order
            await collection.Find(filter).Sort(sort).Limit(5).ForEachAsync(d => Console.WriteLine(d)); // Sorted documents

            // Projections allow us to determine what fields should be included in the documents
            // that are returned.
            Console.WriteLine("Findling all documents with a 'name' field and exclude the '_id' and 'course' fields in the returned documents");
            await collection.Find(Builders<BsonDocument>.Filter.Exists("name"))
                .Project
                (
                        Builders <BsonDocument>.Projection
                        .Exclude("_id")
                        .Exclude("course")
                )
                .ForEachAsync(d => Console.WriteLine(d));


            // Update a single document.
            String name = "Fred Bloggs";
            doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("name", name)).FirstOrDefaultAsync();
            if (doc == null)
            {
                Console.WriteLine("No document found for {0}", name);
            }
            else if (doc.Contains("age"))   // Alternative doc.TryGetValue("age", out ageBson) == true
            {

                // Alternative
                //int age = (int)doc.GetValue("age");
                //doc.Set("age", age + 1);
                doc["age"] = (int)doc["age"] + 1;    
                Console.WriteLine("[{0}] Changed the age of {1} to {2} in the document", doc["_id"], doc["name"], doc["age"]);
            }
            else
            {
                // Use doc.put("age", 22) if you want to add a new field and value instead.
                Console.WriteLine("Error: age field did not exist in document");
            }

            // Note: A new document is added if it does not already exist
            UpdateResult updated;

            // The approach here is to provide a new BSON document with $set explicitly,
            // a slightly easier approach is shown below this.
            updated = await collection.UpdateOneAsync(Builders<BsonDocument>.Filter.Eq("_id", doc["_id"]), new BsonDocument("$set", doc));
            if (updated.IsAcknowledged)
            {
                Console.WriteLine("Matched {0} documents for update and changed {1} documents",
                        updated.MatchedCount,
                        updated.ModifiedCount);

                doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("name", name)).FirstOrDefaultAsync();
                Console.Write("Changed document now contains {0}\n", doc);
            }

            // Easier approach
            updated = await collection.UpdateOneAsync(Builders<BsonDocument>.Filter.Eq("_id", doc["_id"]), 
                                        Builders<BsonDocument>.Update
                                        .Set("age", 35)
                                        .Set("course", "Networks"));   // Set fields

            doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("name", name)).FirstOrDefaultAsync();
            Console.Write("Changed document now contains {0}\n", doc);

            updated = await collection.UpdateOneAsync(Builders<BsonDocument>.Filter.Eq("_id", doc["_id"]), Builders<BsonDocument>.Update.Inc("age", 1));    // Add one to the age
            doc = await collection.Find(Builders<BsonDocument>.Filter.Eq("name", name)).FirstOrDefaultAsync();
            Console.Write("Changed document now contains {0}\n", doc);

            // Updated multiple documents at the same time
            updated = await collection.UpdateManyAsync(Builders<BsonDocument>.Filter.Lt("i", 100), Builders<BsonDocument>.Update.Inc("i", 100));
            if (updated.IsAcknowledged)
            {
                Console.WriteLine("Updating many documents, matched {0} documents and changed {1} documents",
                        updated.MatchedCount,
                        updated.ModifiedCount);

                //await collection.Find(new BsonDocument()).ForEachAsync(d => Console.WriteLine(d)); // Display all
            }

            DeleteResult deleted;

            // Delete one document
            deleted = await collection.DeleteOneAsync(Builders<BsonDocument>.Filter.Eq("i", 110));
            if (deleted.IsAcknowledged)
            {
                Console.WriteLine("Deleted {0} documents",
                    deleted.DeletedCount);
            }

            // Delete many documents
            deleted = await collection.DeleteManyAsync(Builders<BsonDocument>.Filter.Gte("i", 112));
            if (deleted.IsAcknowledged)
            {
                Console.WriteLine("Deleting many documents, deleted {0} documents",
                    deleted.DeletedCount);

                //await collection.Find(new BsonDocument()).ForEachAsync(d => Console.WriteLine(d)); // Display all
            }

            // Delete all documents in collection
            long countBefore = collection.CountDocuments(new BsonDocument());
            deleted = await collection.DeleteManyAsync(new BsonDocument());        // May as well just drop the collection!!
            if (deleted.IsAcknowledged)
            {
                Console.WriteLine("Deleting all documents in the collection, deleted {0} documents, before:{1} after:{2}\n",
                    deleted.DeletedCount,
                    countBefore,
                    collection.CountDocuments(new BsonDocument()));

                await collection.Find(new BsonDocument()).ForEachAsync(d => Console.WriteLine(d)); // Display all
            }

            // Remove the collection and all its content
            await database.DropCollectionAsync("test");

            // Remove the database and all its content       
            await client.DropDatabaseAsync("mydb");

        }

        private static void DisplayDataSmallList(List<BsonDocument> list)
        {
            foreach (BsonDocument doc in list)
            {
                Console.WriteLine(doc.ToJson());
            }
        }
    }
}
